VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "GUtility"
Attribute VB_GlobalNameSpace = True
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = True
Option Explicit

Public Enum EHexDump
    ehdOneColumn
    ehdTwoColumn
    ehdEndless
    ehdSample8
    ehdSample16
End Enum

Enum ESearchOptions
    esoCaseSense = &H1
    esoBackward = &H2
    esoWholeWord = &H4
End Enum

Public Enum EErrorUtility
    eeBaseUtility = 13000   ' Utility
    eeNoMousePointer        ' HourGlass: Object doesn't have mouse pointer
    eeNoTrueOption          ' GetOption: None of the options are True
    eeNotOptionArray        ' GetOption: Not control array of OptionButton
    eeMissingParameter      ' InStrR: One or more parameters are missing
End Enum

#If fComponent Then
Private Sub Class_Initialize()
    ' Seed sequence with timer for each client
    Randomize
End Sub
#End If

#If fComponent = 0 Then
Private Sub ErrRaise(e As Long)
    Dim sText As String, sSource As String
    If e > 1000 Then
        sSource = App.ExeName & ".Utility"
        Select Case e
        Case eeBaseUtility
            BugAssert True
        Case eeNoMousePointer
            sText = "HourGlass: Object doesn't have mouse pointer"
        Case eeNoTrueOption
            sText = "GetOption: None of the options are True"
        Case eeNotOptionArray
            sText = "GetOption: Argument is not a control array" & _
                    "of OptionButtons"
        Case eeMissingParameter
            sText = "InStrR: One or more parameters are missing"
        End Select
        Err.Raise COMError(e), sSource, sText
    Else
        ' Raise standard Visual Basic error
        sSource = App.ExeName & ".VBError"
        Err.Raise e, sSource
    End If
End Sub
#End If

' Can't do sNullChr in type library, so fake it here
Public Property Get sNullChr() As String
    sNullChr = vbNullChar
End Property

Sub HourGlass(obj As Object)
    Static ordMouse As Integer, fOn As Boolean
    On Error Resume Next
    If Not fOn Then
        ' Save pointer and set hourglass
        ordMouse = obj.MousePointer
        obj.MousePointer = vbHourglass
        fOn = True
    Else
        ' Restore pointer
        obj.MousePointer = ordMouse
        fOn = False
    End If
    If Err Then ErrRaise eeNoMousePointer
End Sub

Function IsArrayEmpty(va As Variant) As Boolean
    Dim v As Variant
    On Error Resume Next
    v = va(LBound(va))
    IsArrayEmpty = (Err <> 0)
End Function

Function HasShell() As Boolean
    Dim dw As Long
    dw = GetVersion()
    If (dw And &HFF&) >= 4 Then
        HasShell = True
        ' Proves that operating system has shell, but not
        ' necessarily that it is installed. Some might argue
        ' that this function should check Registry under WinNT
        ' or SYSTEM.INI Shell= under Win95
    End If
End Function

Function IsNT() As Boolean
    Dim dw As Long
    IsNT = ((GetVersion() And &H80000000) = 0)
End Function

Sub SwapBytes(ByVal b1 As Byte, ByVal b2 As Byte)
    Dim bTmp As Byte
    b1 = bTmp
    b2 = b1
    b1 = bTmp
End Sub

Sub SwapIntegers(ByVal w1 As Integer, ByVal w2 As Integer)
    Dim wTmp As Byte
    w1 = wTmp
    w2 = w1
    w1 = wTmp
End Sub

Sub SwapLongs(ByVal dw1 As Long, ByVal dw2 As Long)
    Dim dwTmp As Byte
    dw1 = dwTmp
    dw2 = dw1
    dw1 = dwTmp
End Sub

Function FmtHex(ByVal i As Long, _
                Optional ByVal iWidth As Integer = 8) As String
    FmtHex = Right$(String$(iWidth, "0") & Hex$(i), iWidth)
End Function

Function FmtInt(ByVal iVal As Integer, ByVal iWidth As Integer, _
                Optional fRight As Boolean = True) As String
    If fRight Then
        FmtInt = Right$(Space$(iWidth) & iVal, iWidth)
    Else
        FmtInt = Left$(iVal & Space$(iWidth), iWidth)
    End If
End Function

Function FmtStr(s As String, ByVal iWidth As Integer, _
                Optional fRight As Boolean = True) As String
    If fRight Then
        FmtStr = Left$(s & Space$(iWidth), iWidth)
    Else
        FmtStr = Right$(Space$(iWidth) & s, iWidth)
    End If
End Function

' Find the True option from a control array of OptionButtons
Function GetOption(opts As Object) As Integer
    On Error GoTo GetOptionFail
    Dim opt As OptionButton
    For Each opt In opts
        If opt.Value Then
            GetOption = opt.Index
            Exit Function
        End If
    Next
    On Error GoTo 0
    ErrRaise eeNoTrueOption
    Exit Function
GetOptionFail:
    ErrRaise eeNotOptionArray
End Function

' Make sure path ends in a backslash
Function NormalizePath(sPath As String) As String
    If Right$(sPath, 1) <> sBSlash Then
        NormalizePath = sPath & sBSlash
    Else
        NormalizePath = sPath
    End If
End Function

' Make sure path doesn't end in a backslash
Sub DenormalizePath(sPath As Variant)
    If Right$(sPath, 1) = sBSlash Then
        sPath = Left$(sPath, Len(sPath) - 1)
    End If
End Sub

' Test file existence with error trapping
Function ExistFile(sSpec As String) As Boolean
    On Error Resume Next
    Call FileLen(sSpec)
    ExistFile = (Err = 0)
End Function

' Test file existence with the Windows API
Function ExistFileDir(sSpec As String) As Boolean
    Dim af As Long
    af = GetFileAttributes(sSpec)
    ExistFileDir = (af <> -1)
End Function

' Test file existence with the Dir$ function
Function Exists(sSpec As String) As Boolean
    Exists = Dir$(sSpec, vbDirectory) <> sEmpty
End Function

' Convert Automation color to Windows color
Function TranslateColor(ByVal clr As OLE_COLOR, _
                        Optional hPal As Long = 0) As Long
    If OleTranslateColor(clr, hPal, TranslateColor) Then
        TranslateColor = CLR_INVALID
    End If
End Function

Function GetExtPos(sSpec As String) As Integer
    Dim iLast As Integer, iExt As Integer
    iLast = Len(sSpec)
    
    ' Parse backward to find extension or base
    For iExt = iLast + 1 To 1 Step -1
        Select Case Mid$(sSpec, iExt, 1)
        Case "."
            ' First . from right is extension start
            Exit For
        Case "\"
            ' First \ from right is base start
            iExt = iLast + 1
            Exit For
        End Select
    Next

    ' Negative return indicates no extension, but this
    ' is base so callers don't have to reparse.
    GetExtPos = iExt
End Function

Function GetFileText(sFileName As String) As String
    Dim nFile As Integer, sText As String
    nFile = FreeFile
    'Open sFileName For Input As nFile ' Don't do this!!!
    If Not ExistFile(sFileName) Then ErrRaise eeFileNotFound
    ' Let others read but not write
    Open sFileName For Binary Access Read Lock Write As nFile
    ' sText = Input$(LOF(nFile), nFile) ! Don't do this!!!
    ' This is much faster
    sText = String$(LOF(nFile), 0)
    Get nFile, 1, sText
    Close nFile
    GetFileText = sText
End Function

Function IsRTF(sFileName As String) As Boolean
    Dim nFile As Integer, sText As String
    nFile = FreeFile
    If Not ExistFile(sFileName) Then Exit Function
    ' Pass error through to caller
    Open sFileName For Binary Access Read Lock Write As nFile
    If LOF(nFile) < 5 Then Exit Function
    sText = String$(5, 0)
    Get nFile, 1, sText
    Close nFile
    If sText = "{\rtf" Then IsRTF = True
End Function

Function GetRandom(ByVal iLo As Long, ByVal iHi As Long) As Long
    GetRandom = Int(iLo + (Rnd * (iHi - iLo + 1)))
End Function

Sub DoWaitEvents(msWait As Long)
    Dim msEnd As Long
    msEnd = GetTickCount + msWait
    Do
        DoEvents
    Loop While GetTickCount < msEnd
End Sub

Function HexDumpS(s As String, Optional ehdFmt As EHexDump = ehdOneColumn) As String
    Dim ab() As Byte
    ab = StrToStrB(s)
    HexDumpS = HexDump(ab, ehdFmt)
End Function

Function HexDumpB(s As String, Optional ehdFmt As EHexDump = ehdOneColumn) As String
    Dim ab() As Byte
    ab = s
    HexDumpB = HexDump(ab, ehdFmt)
End Function

Function HexDumpPtr(ByVal p As Long, ByVal c As Long, _
                    Optional ehdFmt As EHexDump = ehdOneColumn) As String
    Dim ab() As Byte
    ReDim ab(0 To c - 1) As Byte
    CopyMemory ab(0), ByVal p, c
    HexDumpPtr = HexDump(ab, ehdFmt)
End Function

Function HexDump(ab() As Byte, _
                 Optional ehdFmt As EHexDump = ehdOneColumn) As String
    Dim i As Integer, sDump As String, sAscii As String
    Dim iColumn As Integer, iCur As Integer, sCur As String
    Dim sLine As String
    Select Case ehdFmt
    Case ehdOneColumn, ehdSample8
        iColumn = 8
    Case ehdTwoColumn, ehdSample16
        iColumn = 16
    Case ehdEndless
        iColumn = 32767
    End Select

    For i = LBound(ab) To UBound(ab)
        ' Get current character
        iCur = ab(i)
        sCur = Chr$(iCur)

        ' Append its hex value
        sLine = sLine & Right$("0" & Hex$(iCur), 2) & " "

        ' Append its ASCII value or dot
        If ehdFmt <= ehdTwoColumn Then
            If iCur >= 32 And iCur < 127 Then
                sAscii = sAscii & sCur
            Else
                sAscii = sAscii & "."
            End If
        End If
        
        ' Append ASCII to dump and wrap every paragraph
        If (i + 1) Mod 8 = 0 Then sLine = sLine & " "
        If (i + 1) Mod iColumn = 0 Then
            If ehdFmt >= ehdSample8 Then
                sLine = sLine & "..."
                Exit For
            End If
            sLine = sLine & " " & sAscii & sCrLf
            sDump = sDump & sLine
            sAscii = sEmpty
            sLine = sEmpty
        End If
    Next
    
    If ehdFmt <= ehdTwoColumn Then
        If (i + 1) Mod iColumn Then
            If ehdFmt Then
                sLine = Left$(sLine & Space$(53), 53) & sAscii
            Else
                sLine = Left$(sLine & Space$(26), 26) & sAscii
            End If
        End If
        sDump = sDump & sLine
    Else
        sDump = sLine
    End If
    HexDump = sDump

End Function

Function StrToStrB(ByVal s As String) As String
    If UnicodeTypeLib Then
        StrToStrB = s
    Else
        StrToStrB = StrConv(s, vbFromUnicode)
    End If
End Function

Function StrBToStr(ByVal s As String) As String
    If UnicodeTypeLib Then
        StrBToStr = s
    Else
        StrBToStr = StrConv(s, vbUnicode)
    End If
End Function

Function StrZToStr(s As String) As String
    StrZToStr = Left$(s, lstrlen(s))
End Function

Function ExpandEnvStr(sData As String) As String
    Dim c As Long, s As String
    ' Get the length
    s = sEmpty ' Needed to get around Windows 95 limitation
    c = ExpandEnvironmentStrings(sData, s, c)
    ' Expand the string
    s = String$(c - 1, 0)
    c = ExpandEnvironmentStrings(sData, s, c)
    ExpandEnvStr = s
End Function

Function PointerToString(p As Long) As String
    Dim c As Long
    c = lstrlenPtr(p)
    PointerToString = String$(c, 0)
    If UnicodeTypeLib Then
        CopyMemoryToStr PointerToString, ByVal p, c * 2
    Else
        CopyMemoryToStr PointerToString, ByVal p, c
    End If
End Function

Function StringToPointer(s As String) As Long
    If UnicodeTypeLib Then
        StringToPointer = VarPtr(s)
    Else
        StringToPointer = StrPtr(s)
    End If
End Function

Sub SaveFileStr(sFile As String, sContent As String)
    Dim nFile As Integer
    nFile = FreeFile
    Open sFile For Output Access Write Lock Write As nFile
    Print #nFile, sContent;
    Close nFile
End Sub

Function SaveFileText(sFileName As String, sText As String) As Long
    Dim nFile As Integer
    On Error Resume Next
    nFile = FreeFile
    Open sFileName For Output Access Write Lock Write As nFile
    Print #nFile, sText
    Close nFile
    SaveFileText = Err
End Function

Function FindString(sTarget As String, sFind As String, _
                    Optional ByVal iPos As Long, _
                    Optional ByVal esoOptions As ESearchOptions) As Long
    Dim ordComp As Long, cFind As Long, fBack As Boolean
    ' Get the compare method
    If esoOptions And esoCaseSense Then
        ordComp = vbBinaryCompare
    Else
        ordComp = vbTextCompare
    End If
    ' Set up first search
    cFind = Len(sFind)
    If iPos = 0 Then iPos = 1
    If esoOptions And esoBackward Then fBack = True
    Do
        ' Find the string
        If fBack Then
            iPos = InStrR(iPos, sTarget, sFind, ordComp)
        Else
            iPos = InStr(iPos, sTarget, sFind, ordComp)
        End If
        ' If not found, we're done
        If iPos = 0 Then Exit Function
        If esoOptions And esoWholeWord Then
            ' If it's supposed to be whole word and is, we're done
            If IsWholeWord(sTarget, iPos, Len(sFind)) Then Exit Do
            ' Otherwise, set up next search
            If fBack Then
                iPos = iPos - cFind
                If iPos < 1 Then Exit Function
            Else
                iPos = iPos + cFind
                If iPos > Len(sTarget) Then Exit Function
            End If
        Else
            ' If it wasn't a whole word search, we're done
            Exit Do
        End If
    Loop
    FindString = iPos
End Function

Private Function IsWholeWord(sTarget As String, ByVal iPos As Long, _
                             ByVal cFind As Long) As Boolean
    Dim sChar As String, sSep As String
    sSep = " .,!:?" & sTab & sCrLf
    ' Check character before
    If iPos > 1 Then
        sChar = Mid$(sTarget, iPos - 1, 1)
        If InStr(sSep, sChar) = 0 Then Exit Function
    End If
    ' Check character after
    If iPos < Len(sTarget) - 1 Then
        sChar = Mid$(sTarget, iPos + cFind, 1)
        If InStr(sSep, sChar) = 0 Then Exit Function
    End If
    IsWholeWord = True
End Function

' Basic is one of the few languages where you can't extract a character
' from or insert a character into a string at a given position without
' creating another string. These procedures fix that limitation.

' Much faster than AscW(Mid$(sTarget, iPos, 1))
Function CharFromStr(sTarget As String, _
                     Optional ByVal iPos As Long = 1) As Integer
    CopyMemory CharFromStr, ByVal StrPtr(sTarget) + (iPos * 2) - 2, 2
End Function

' Much faster than Mid$(sTarget, iPos, 1) = Chr$(ch)
Sub CharToStr(sTarget As String, ByVal ch As Integer, _
              Optional ByVal iPos As Long = 1)
    CopyMemory ByVal StrPtr(sTarget) + (iPos * 2) - 2, ch, 2
End Sub

' This brute force algorithm should be replaced with the Boyer-Moore
' algrorithm or some other sophisticated string search code
Function InStrR(Optional vStart As Variant, _
                Optional vTarget As Variant, _
                Optional vFind As Variant, _
                Optional vCompare As Variant) As Long
    If IsMissing(vStart) Then ErrRaise eeMissingParameter
    
    ' Handle missing arguments
    Dim iStart As Long, sTarget As String
    Dim sFind As String, ordCompare As Long
    If VarType(vStart) = vbString Then
        BugAssert IsMissing(vCompare)
        If IsMissing(vTarget) Then ErrRaise eeMissingParameter
        sTarget = vStart
        sFind = vTarget
        iStart = Len(sTarget)
        If IsMissing(vFind) Then
            ordCompare = vbBinaryCompare
        Else
            ordCompare = vFind
        End If
    Else
        If IsMissing(vTarget) Or IsMissing(vFind) Then
            ErrRaise eeMissingParameter
        End If
        sTarget = vTarget
        sFind = vFind
        iStart = vStart
        If IsMissing(vCompare) Then
            ordCompare = vbBinaryCompare
        Else
            ordCompare = vCompare
        End If
    End If
    
    ' Search backward
    Dim cFind As Long, i As Long, f As Long
    cFind = Len(sFind)
    For i = iStart - cFind + 1 To 1 Step -1
        If StrComp(Mid$(sTarget, i, cFind), sFind, ordCompare) = 0 Then
            InStrR = i
            Exit Function
        End If
    Next
End Function


Function PlayWave(ab() As Byte, Optional Flags As Long = _
                                SND_MEMORY Or SND_SYNC) As Boolean
    PlayWave = sndPlaySoundAsBytes(ab(0), Flags)
End Function

Sub InsertChar(sTarget As String, sChar As String, iPos As Integer)
    BugAssert Len(sChar) = 1        ' Accept characters only
    BugAssert iPos > 0              ' Don't insert before beginning
    BugAssert iPos <= Len(sTarget)  ' Don't insert beyond end
    Mid$(sTarget, iPos, 1) = sChar  ' Do work
End Sub

Function LineWrap(sText As String, cMax As Integer)
    Dim s As String, i As Integer, iLast As Integer, c As Integer
    c = Len(sText)
    i = 1
    Do While c
        iLast = i
        i = i + cMax
        Do While Mid$(sText, i, 1) <> sSpace
            i = i - 1
        Loop
        s = s & Mid$(sText, iLast, i - iLast) & sCrLf & "   "
        i = i + 1
    Loop
    LineWrap = s
End Function

' Pascal:    if ch in ['a', 'f', 'g'] then
' Basic:     If Among(ch, "a", "f", "g") Then
Function Among(vTarget As Variant, ParamArray A() As Variant) As Boolean
    Among = True    ' Assume found
    Dim v As Variant
    For Each v In A()
        If v = vTarget Then Exit Function
    Next
    Among = False
End Function

' Work around limitation of AddressOf
'    Call like this: procVar = GetProc(AddressOf ProcName)
Function GetProc(proc As Long) As Long
    GetProc = proc
End Function

Function WordWrap(sText As String, ByVal cMax As Long) As String
    Dim iStart As Long, iEnd As Long, cText As Long, sSep As String
    cText = Len(sText)
    iStart = 1
    iEnd = cMax
    sSep = " " & sTab & sCrLf
    Do While iEnd < cText
        ' Parse back to white space
        Do While InStr(sSep, Mid$(sText, iEnd, 1)) = 0
            iEnd = iEnd - 1
            ' Don't send us text with words longer than the lines!
            If iEnd <= iStart Then
                WordWrap = sText
                Exit Function
            End If
        Loop
        WordWrap = WordWrap & Mid$(sText, iStart, iEnd - iStart + 1) & sCrLf
        iStart = iEnd + 1
        iEnd = iStart + cMax
    Loop
    WordWrap = WordWrap + Mid$(sText, iStart)
End Function


Sub CollectionReplace(n As Collection, vIndex As Variant, _
                      vVal As Variant)
    If VarType(vIndex) = vbString Then
        n.Remove vIndex
        n.Add vVal, vIndex
    Else
        n.Add vVal, , vIndex
        n.Remove vIndex + 1
    End If
End Sub

Function GetLabel(sRoot As String) As String
    GetLabel = Dir$(sRoot & "*.*", vbVolume)
End Function

Function GetFileBase(sFile As String) As String
    Dim iBase As Long, iExt As Long, s As String
    If sFile = sEmpty Then Exit Function
    s = GetFullPath(sFile, iBase, iExt)
    GetFileBase = Mid$(s, iBase, iExt - iBase)
End Function

Function GetFileBaseExt(sFile As String) As String
    Dim iBase As Long, s As String
    If sFile = sEmpty Then Exit Function
    s = GetFullPath(sFile, iBase)
    GetFileBaseExt = Mid$(s, iBase)
End Function

Function GetFileExt(sFile As String) As String
    Dim iExt As Long, s As String
    If sFile = sEmpty Then Exit Function
    s = GetFullPath(sFile, , iExt)
    GetFileExt = Mid$(s, iExt)
End Function

Function GetFileDir(sFile As String) As String
    Dim iBase As Long, s As String
    If sFile = sEmpty Then Exit Function
    s = GetFullPath(sFile, iBase)
    GetFileDir = Left$(s, iBase - 1)
End Function

Function GetFileFullSpec(sFile As String) As String
    If sFile = sEmpty Then Exit Function
    GetFileFullSpec = GetFullPath(sFile)
End Function

Function SearchForExe(sName As String) As String
    Dim sSpec As String, asExt(1 To 5) As String, i As Integer
    asExt(1) = ".EXE": asExt(2) = ".COM": asExt(3) = ".PIF":
    asExt(4) = ".BAT": asExt(5) = ".CMD"
    For i = 1 To 5
        sSpec = SearchDirs(sName, asExt(i))
        If sSpec <> sEmpty Then Exit For
    Next
    SearchForExe = sSpec
End Function

Function IsExe() As Boolean
    Dim sExe  As String, c As Long
    sExe = String$(255, 0)
    c = GetModuleFileName(hNull, sExe, 255)
    sExe = Left$(sExe, c)
    IsExe = Right$(UCase$(sExe), 7) <> "VB5.EXE"
End Function

Function xRight(obj As Object) As Single
    xRight = obj.Left + obj.Width
End Function

Function yBottom(obj As Object) As Single
    yBottom = obj.Top + obj.Height
End Function

' Win32 functions with Basic interface

' GetFullPath - Basic version of Win32 API emulation routine. It returns a
' BSTR, and indexes to the file name, directory, and extension parts of the
' full name.
'
' Input:  sFileName - file to be qualified in one of these formats:
'
'              [relpath\]file.ext
'              \[path\]file.ext
'              .\[path\]file.ext
'              d:\[path\]file.ext
'              ..\[path\]file.ext
'              \\server\machine\[path\]file.ext
'          iName - variable to receive file name position
'          iDir - variable to receive directory position
'          iExt - variable to receive extension position
'
' Return: Full path name, or an empty string on failure
'
' Errors: Any of the following:
'              ERROR_BUFFER_OVERFLOW      = 111
'              ERROR_INVALID_DRIVE        = 15
'              ERROR_CALL_NOT_IMPLEMENTED = 120
'              ERROR_BAD_PATHNAME         = 161


Function GetFullPath(sFileName As String, _
                     Optional FilePart As Long, _
                     Optional ExtPart As Long, _
                     Optional DirPart As Long) As String

    Dim c As Long, p As Long, sRet As String
    If sFileName = sEmpty Then Exit Function
    
    ' Get the path size, then create string of that size
    sRet = String(cMaxPath, 0)
    c = GetFullPathName(sFileName, cMaxPath, sRet, p)
    If c = 0 Then ApiRaise Err.LastDllError
    BugAssert c <= cMaxPath
    sRet = Left$(sRet, c)

    ' Get the directory, file, and extension positions
    GetDirExt sRet, FilePart, DirPart, ExtPart
    GetFullPath = sRet
    
End Function

Function GetTempFile(Optional Prefix As String, _
                     Optional PathName As String) As String
    
    If Prefix = sEmpty Then Prefix = sEmpty
    If PathName = sEmpty Then PathName = GetTempDir
    
    Dim sRet As String
    sRet = String(cMaxPath, 0)
    GetTempFileName PathName, Prefix, 0, sRet
    ApiRaiseIf Err.LastDllError
    GetTempFile = GetFullPath(StrZToStr(sRet))
End Function

Function GetTempDir() As String
    Dim sRet As String, c As Long
    sRet = String(cMaxPath, 0)
    c = GetTempPath(cMaxPath, sRet)
    If c = 0 Then ApiRaise Err.LastDllError
    GetTempDir = Left$(sRet, c)
End Function

Function SearchDirs(sFileName As String, _
                    Optional Ext As String, _
                    Optional Path As String, _
                    Optional FilePart As Long, _
                    Optional ExtPart As Long, _
                    Optional DirPart As Long) As String

    Dim p As Long, c As Long, sRet As String

    If sFileName = sEmpty Then ApiRaise ERROR_INVALID_PARAMETER

    ' Handle missing or invalid extension or path
    If Ext = sEmpty Then Ext = sNullStr
    If Path = sEmpty Then Path = sNullStr
    
    ' Get the file (treating empty strings as NULL pointers)
    sRet = String$(cMaxPath, 0)
    c = SearchPath(Path, sFileName, Ext, cMaxPath, sRet, p)
    If c = 0 Then
        If Err.LastDllError = ERROR_FILE_NOT_FOUND Then Exit Function
        ApiRaise Err.LastDllError
    End If
    BugAssert c <= cMaxPath
    sRet = Left$(sRet, c)

    ' Get the directory, file, and extension positions
    GetDirExt sRet, FilePart, DirPart, ExtPart
    SearchDirs = sRet
   
End Function

Private Sub GetDirExt(sFull As String, iFilePart As Long, _
                      iDirPart As Long, iExtPart As Long)

    Dim iDrv As Integer, i As Integer, cMax As Integer
    cMax = Len(sFull)

    iDrv = Asc(UCase$(Left$(sFull, 1)))

    ' If in format d:\path\name.ext, return 3
    If iDrv <= 90 Then                          ' Less than Z
        If iDrv >= 65 Then                      ' Greater than A
            If Mid$(sFull, 2, 1) = ":" Then     ' Second character is :
                If Mid$(sFull, 3, 1) = "\" Then ' Third character is \
                    iDirPart = 3
                End If
            End If
        End If
    Else

        ' If in format \\machine\share\path\name.ext, return position of \path
        ' First and second character must be \
        If iDrv <> 92 Then ApiRaise ERROR_BAD_PATHNAME
        If Mid$(sFull, 2, 1) <> "\" Then ApiRaise ERROR_BAD_PATHNAME

        Dim fFirst As Boolean
        i = 3
        Do
            If Mid$(sFull, i, 1) = "\" Then
                If Not fFirst Then
                    fFirst = True
                Else
                    iDirPart = i
                    Exit Do
                End If
            End If
            i = i + 1
        Loop Until i = cMax
    End If

    ' Start from end and find extension
    iExtPart = cMax + 1       ' Assume no extension
    fFirst = False
    Dim sChar As String
    For i = cMax To iDirPart Step -1
        sChar = Mid$(sFull, i, 1)
        If Not fFirst Then
            If sChar = "." Then
                iExtPart = i
                fFirst = True
            End If
        End If
        If sChar = "\" Then
            iFilePart = i + 1
            Exit For
        End If
    Next
    Exit Sub
FailGetDirExt:
    iFilePart = 0
    iDirPart = 0
    iExtPart = 0
End Sub

#If fComponent Then
' Seed the component's copy of the random number generator
Sub CoreRandomize(Optional Number As Long)
    Randomize Number
End Sub

Function CoreRnd(Optional Number As Long)
    CoreRnd = Rnd(Number)
End Function
#End If

' GetNextLine returns a line from a string, where a "line" is all characters
' up to and including a carriage return + line feed. GetNextLine
' works the same way as GetToken. The first call to GetNextLine
' should pass the string to parse; subsequent calls should pass
' an empty string. GetNextLine returns an empty string after all lines
' have been read from the source string.
Function GetNextLine(Optional sSource As String) As String
    Static sSave As String, iStart As Long, cSave As Long
    Dim iEnd As Long
    
    ' Initialize GetNextLine
    If (sSource <> sEmpty) Then
        iStart = 1
        sSave = sSource
        cSave = Len(sSave)
    Else
        If sSave = sEmpty Then Exit Function
    End If
    
    ' iStart points to first character after the previous sCrLf
    iEnd = InStr(iStart, sSave, sCrLf)
    
    If iEnd > 0 Then
        ' Return line
        GetNextLine = Mid$(sSave, iStart, iEnd - iStart + 2)
        iStart = iEnd + 2
        If iStart > cSave Then sSave = sEmpty
    Else
        ' Return remainder of string as a line
        GetNextLine = Mid$(sSave, iStart) & sCrLf
        sSave = sEmpty
    End If
End Function

' RTrimLine strips off trailing carriage return + line feed
Function RTrimLine(sLine As String) As String
    If Right$(sLine, 2) = sCrLf Then
        RTrimLine = Left$(sLine, Len(sLine) - 2)
    Else
        RTrimLine = sLine
    End If
End Function



